
/* amazing particle generator*/

#if defined(_WIN32) || defined(_WIN64)
    #define PLATFORM_WINDOWS 1
#else
    #define PLATFORM_WINDOWS 0
#endif

#if (defined(__unix__) || defined(__unix) || (defined(__APPLE__) && defined(__MACH__)) || defined(_POSIX_VERSION))
    #define PLATFORM_POSIX 1
#else
    #define PLATFORM_POSIX 0
#endif

#if PLATFORM_POSIX
#include <csignal>
#include <termios.h>
#endif

#if PLATFORM_WINDOWS
#include <windows.h>
#endif

#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <stack>
#include <unistd.h>
#include <vector>
#include <cmath>
#include <thread>


/* ********************  UTILS begin ******************** */

namespace platform {
    void sleep(double sec) {
        std::this_thread::sleep_for(std::chrono::duration<double>(sec));
    }
}

/* ********************  UTILS end ******************** */


/* ********************  TERMINAL DISPLAY DRIVER begin ******************** */

namespace termdrv {
    void termdrv_init();
    void termdrv_prepare();

    void termdrv_move_cursor(int x, int y);
    void termdrv_set_cursor_visible(bool visible);
    void termdrv_put_char(char c);
    void termdrv_get_term_size(int *cols, int *rows);

    /* maybe generic implementation */
    void termdrv_put_char(char c) {
        putchar(c);
    }
}

#if PLATFORM_POSIX
namespace termdrv {
    static termios _g_saved_termios;

    void vt_set_uncanonical() {
        termios t;
        tcgetattr(STDIN_FILENO, &t);
        t.c_lflag &= ~(ICANON | ECHO);
        t.c_cc[VMIN] = 0;
        t.c_cc[VMIN] = 1;
        tcsetattr(STDIN_FILENO, TCSANOW, &t);
    }

    void vt_init_terminal() {
        vt_set_uncanonical();
    }

    void vt_termial_reset() {
        tcsetattr(STDIN_FILENO, TCSANOW, &_g_saved_termios);
    }

    void termdrv_init() {}

    void termdrv_prepare() {
        vt_set_uncanonical();
    }

    void termdrv_move_cursor(int x, int y) {

    }

    void termdrv_set_cursor_visible(bool visible) {}

    void termdrv_get_term_size(int *cols, int *rows) {}
};
#endif

#if PLATFORM_WINDOWS
namespace termdrv {
    static DWORD _g_original_console_mode;

    void termdrv_init() {}

    void termdrv_prepare() {
        HANDLE h_stdin = GetStdHandle(STD_INPUT_HANDLE);
        GetConsoleMode(h_stdin, &_g_original_console_mode);

        DWORD new_mode = _g_original_console_mode;
        new_mode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT);
        SetConsoleMode(h_stdin, new_mode);
    }

    void termdrv_move_cursor(int x, int y) {
        HANDLE h_console = GetStdHandle(STD_OUTPUT_HANDLE);
        COORD pos = { (SHORT)x, (SHORT)y };
        SetConsoleCursorPosition(h_console, pos);
    }

    void termdrv_get_term_size(int *cols, int *rows)    {
        HANDLE h_console = GetStdHandle(STD_OUTPUT_HANDLE);
        CONSOLE_SCREEN_BUFFER_INFO csbi;
        GetConsoleScreenBufferInfo(h_console, &csbi);

        *cols = csbi.srWindow.Right - csbi.srWindow.Left + 1;
        *rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
    }

    void termdrv_set_cursor_visible(bool visible) {
        HANDLE h_console = GetStdHandle(STD_OUTPUT_HANDLE);
        CONSOLE_CURSOR_INFO cursorInfo;
        GetConsoleCursorInfo(h_console, &cursorInfo);
        cursorInfo.bVisible = visible;  // TRUE 显示，FALSE 隐藏
        SetConsoleCursorInfo(h_console, &cursorInfo);
    }
}
#endif

/* ******************** TERMINAL DISPLAY DRIVER end ******************** */

/* ******************** RENDER LAYER begin ******************** */

namespace render {
    /**
     *    +--------+ tr (x, y)
     *    |        |
     *    |        |
     *    |        |
     *    |        |
     *    +--------+
     * br(x, y)
     */
    struct Viewport {
        float bl_x, bl_y;
        float tr_x, tr_y;

        void proj_to_term(float wx, float wy, int *sx, int *sy) const {
            int cols, rows;
            float h = abs(bl_x - tr_x);
            float w = abs(bl_y - tr_y);

            float scr_x = (wx - bl_x) / w, src_y = (wx - bl_y);

            termdrv::termdrv_get_term_size(&cols, &rows);

            /* map to terminal */

            *sx = int(float(cols) * scr_x);
            *sy = int(float(rows) * src_y);
        }
    };
}

/* ******************** RENDER LAYER end ******************** */


namespace particle {
    namespace __details {
        template<typename T>
        struct SequenceMemoryPool {
            struct _BlockPtrProxy {
                _BlockPtrProxy(SequenceMemoryPool *b, size_t idx)
                    : _m_block(b), idx(idx) {}

                T& operator*() {
                    return *(T*)(&_m_block->_m_data[idx]);
                }

                T* operator->() {
                    return (T*)(&_m_block->_m_data[idx]);
                }

                T* ptr() {
                    return (T*)(&_m_block->_m_data[idx]);
                }
            private:
                SequenceMemoryPool *_m_block;
                size_t idx;

                friend struct SequenceMemoryPool;
            };

            struct _BlockIterator {
                _BlockIterator& operator++() {
                    _m_idx += _m_stride;
                    return *this;
                }
                
                _BlockIterator operator++(int) {
                    auto tmp = *this;
                    _m_idx += _m_stride;
                    return tmp;
                }

                T& operator*() {
                    auto ptr = &_m_mem_pool->_m_data[_m_idx];
                    return *(T*)ptr;
                }

                bool operator==(const _BlockIterator& rhs) {
                    return _m_idx == rhs._m_idx;
                }

                bool operator!=(const _BlockIterator& rhs) {
                    return _m_idx != rhs._m_idx;
                }
                
                size_t _m_stride;
                SequenceMemoryPool<T> *_m_mem_pool;
                size_t _m_idx;
            };

            explicit SequenceMemoryPool(size_t block_size)
                : _m_block_size(block_size) {}

            _BlockPtrProxy alloc_block() {
                if (_m_free_idx.empty()) {
                    auto size = _m_data.size();
                    _m_data.resize(size + _m_block_size);
                    return _BlockPtrProxy(this, size);
                } else {
                    size_t idx = _m_free_idx.top();
                    _m_free_idx.pop();
                    return _BlockPtrProxy(this, idx);
                }
            }

            _BlockIterator begin() {
                return _BlockIterator {
                    ._m_stride = _m_block_size,
                    ._m_mem_pool = this,
                    ._m_idx = 0,
                };
            }

            _BlockIterator end() {
                return _BlockIterator {
                    ._m_stride = _m_block_size,
                    ._m_mem_pool = this,
                    ._m_idx = _m_data.size(),
                };
            }
        private:
            size_t _m_block_size;
            std::vector<int8_t> _m_data;
            std::stack<size_t> _m_free_idx;
        };
    };

    struct ParticleEntity {
        bool active{true};
        bool visible{true};
        float x{0}, y{0}; 
    };

    struct ParticleBehavior {
        virtual ~ParticleBehavior() = default;

        struct OnConstructInput {
            ParticleEntity *entity;
        };

        struct OnTickInput {
            ParticleEntity *entity;
            float delta;
        };

        virtual void on_construct(const OnConstructInput &in) = 0;
        virtual void on_tick(const OnTickInput &in) = 0;
    };

    namespace __details {
        struct DefaultParticleBehavior : ParticleBehavior {
            static ParticleBehavior * const INSTANCE;

            void on_construct(const OnConstructInput &in) override {}
            void on_tick(const OnTickInput &in) override {}
        };
        ParticleBehavior*
            const DefaultParticleBehavior::INSTANCE = new DefaultParticleBehavior{};
    }

    struct ParticleCreateInfo {
        size_t extra_data_size{0};
        ParticleBehavior *behavior{nullptr};
    };

    struct ParticleSystem {
        struct RenderInfo {
            render::Viewport viewport;
        };

        explicit ParticleSystem(const ParticleCreateInfo &info)
            : _m_create_info(info), 
              _m_pool(info.extra_data_size + sizeof(ParticleEntity)) {
            
            if (_m_create_info.behavior == nullptr) {
                _m_create_info.behavior = __details::DefaultParticleBehavior::INSTANCE; 
            }
        }

        void emit() {
            auto&& entity = _m_pool.alloc_block();

            _m_create_info.behavior->on_construct( {entity.ptr()} );
        }
        
        void tick(float delta) {
            auto&& behavior = _m_create_info.behavior;
            for (auto&& entity : _m_pool) {
                if (entity.active) {
                    behavior->on_tick( { &entity, delta } );
                }
            }
        }

        void render(const RenderInfo &info) {
            for (auto&& entity : _m_pool) {
                if (entity.active && entity.visible) {
                    int sx, sy;
                    info.viewport.proj_to_term(entity.x, entity.y, &sx, &sy);
                    termdrv::termdrv_put_char('*');
                }
            }
        }

    private:
        ParticleCreateInfo _m_create_info;
        __details::SequenceMemoryPool<ParticleEntity> _m_pool;
    };
};

namespace dummy {
    struct DummyParticleBehavior final : particle::ParticleBehavior {
        void on_construct(const OnConstructInput &in) override {

        }

        void on_tick(const OnTickInput &in) override {
            auto&& entity = in.entity;
            entity->x = std::sin(in.delta) * 0.2f + 0.5f;
            entity->y = std::cos(in.delta) * 0.2f + 0.5f;
        }
    };

    void main() {
        DummyParticleBehavior behavior;

        particle::ParticleCreateInfo info {
            .extra_data_size = 0,
            .behavior = &behavior,
        };

        particle::ParticleSystem system(info);

        system.emit();

        while (true) {

        }
    }

}

int main(int argc, char *argv[]) {
    dummy::main();

    return 0;
}
